﻿// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Generator.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Azure.Provisioning.Generator.Model;

public class SimpleModel(Specification spec, Type armType, string name, string? ns = default, string? description = default)
    : TypeModel(spec, armType, name, ns, description)
{
    public override string ToString() => $"<Model {Namespace}.{Name}>";

    public override void Generate()
    {
        ContextualException.WithContext(
            $"Generating simple model {Namespace}.{Name}",
            () =>
            {
                IndentWriter writer = new();
                writer.WriteLine("// Copyright (c) Microsoft Corporation. All rights reserved.");
                writer.WriteLine("// Licensed under the MIT License.");
                writer.WriteLine();
                writer.WriteLine("// <auto-generated/>");
                writer.WriteLine();
                writer.WriteLine("#nullable enable");
                writer.WriteLine();

                var fence = new IndentWriter.Fenceposter();
                HashSet<string> namespaces = CollectNamespaces();
                if (FromExpression) { namespaces.Add("Azure.Provisioning.Expressions"); }
                if (FromExpression) { namespaces.Add("System.ComponentModel"); }
                foreach (string ns in namespaces.Order())
                {
                    if (fence.RequiresSeparator) { /* Don't write anything here */ }
                    writer.WriteLine($"using {ns};");
                }
                if (fence.RequiresSeparator) { writer.WriteLine(); }

                writer.WriteLine($"namespace {Namespace};");
                writer.WriteLine();
                writer.WriteLine($"/// <summary>");
                writer.WriteWrapped(Description ?? (Name + "."));
                writer.WriteLine($"/// </summary>");
                writer.WriteLine($"public partial class {Name} : {(BaseType is not null ? BaseType.Name : "ProvisionableConstruct")}");
                using (writer.Scope("{", "}"))
                {
                    fence = new IndentWriter.Fenceposter();

                    // Write the properties
                    foreach (Property property in Properties)
                    {
                        // we do not write those hidden properties with EditorBrowsableAttribute, these should be added back by customized code in partial classes
                        if (property.HideLevel.HasFlag(PropertyHideLevel.HideProperty))
                        {
                            continue;
                        }
                        if (fence.RequiresSeparator) { writer.WriteLine(); }
                        if (!property.HideAccessors)
                        {
                            writer.WriteLine($"/// <summary>");
                            string orSets = property.IsReadOnly ? "" : " or sets";
                            writer.WriteWrapped(property.Description ?? $"Gets{orSets} the {property.Name}.");
                            writer.WriteLine($"/// </summary>");
                            writer.WriteLine($"public {property.BicepTypeReference} {property.Name} ");
                            using (writer.Scope("{", "}"))
                            {
                                writer.WriteLine($"get {{ Initialize(); return {property.FieldName}!; }}");
                                if (!property.IsReadOnly)
                                {
                                    writer.Write($"set {{ Initialize(); ");
                                    if (property.PropertyType is SimpleModel || property.PropertyType is Resource)
                                    {
                                        writer.Write($"AssignOrReplace(ref {property.FieldName}, value);");
                                    }
                                    else
                                    {
                                        writer.Write($"{property.FieldName}!.Assign(value);");
                                    }
                                    writer.WriteLine($" }}");
                                }
                            }
                        }
                        writer.WriteLine($"private {property.BicepTypeReference}? {property.FieldName};");
                    }

                    // Write the default value partial methods
                    foreach (Property property in Properties.Where(p => p.GenerateDefaultValue))
                    {
                        if (fence.RequiresSeparator) { writer.WriteLine(); }

                        writer.WriteLine($"/// <summary>");
                        writer.WriteWrapped($"Get the default value for the {property.Name} property.");
                        writer.WriteLine($"/// </summary>");
                        writer.WriteLine($"private partial {property.BicepTypeReference} Get{property.Name}DefaultValue();");
                    }

                    // Write the .ctor
                    if (fence.RequiresSeparator) { writer.WriteLine(); }
                    writer.WriteLine($"/// <summary>");
                    writer.WriteWrapped($"Creates a new {Name}.");
                    writer.WriteLine($"/// </summary>");
                    writer.Write($"public {Name}()");
                    if (BaseType is not null)
                    {
                        writer.Write(" : base()");
                    }
                    writer.WriteLine();
                    using (writer.Scope("{", "}")) { }

                    // Write the properties
                    if (fence.RequiresSeparator) { writer.WriteLine(); }
                    writer.WriteLine($"/// <summary>");
                    writer.WriteWrapped($"Define all the provisionable properties of {Name}.");
                    writer.WriteLine($"/// </summary>");
                    writer.WriteLine($"protected override void DefineProvisionableProperties()");
                    using (writer.Scope("{", "}"))
                    {
                        writer.WriteLine("base.DefineProvisionableProperties();");
                        if (DiscriminatorName is not null)
                        {
                            writer.WriteLine($"DefineProperty<string>(\"{DiscriminatorName}\", [\"{DiscriminatorName}\"], defaultValue: \"{DiscriminatorValue}\");");
                        }
                        foreach (Property property in Properties)
                        {
                            if (property.HideLevel.HasFlag(PropertyHideLevel.HideField))
                            {
                                continue; // Skip hidden properties
                            }
                            writer.Write($"{property.FieldName} = ");
                            if (property.PropertyType is SimpleModel || property.PropertyType is Resource)
                            {
                                writer.Write($"DefineModelProperty");
                            }
                            else if (property.PropertyType is ListModel lst)
                            {
                                writer.Write($"DefineListProperty");
                            }
                            else if (property.PropertyType is DictionaryModel dict)
                            {
                                writer.Write($"DefineDictionaryProperty");
                            }
                            else
                            {
                                writer.Write($"DefineProperty");
                            }
                            writer.Write($"<{property.BicepPropertyTypeReference}>(\"{property.Name}\", ");
                            writer.Write($"[{string.Join(", ", (property.Path ?? [property.Name]).Select(s => $"\"{s}\""))}]");
                            if (property.PropertyType is Resource r)
                            {
                                writer.Write($", new {r.Name}(\"{r.Name.ToCamelCase()}\")");
                            }
                            if (property.IsRequired) { writer.Write($", isRequired: true"); }
                            if (property.IsReadOnly) { writer.Write($", isOutput: true"); }
                            if (property.IsSecure) { writer.Write($", isSecure: true"); }
                            if (property.GenerateDefaultValue) { writer.Write($", defaultValue: Get{property.Name}DefaultValue()"); }
                            if (property.Format is not null) { writer.Write($", format: \"{property.Format}\""); }
                            writer.WriteLine($");");

                            if (GeneratePartialPropertyDefinition)
                            {
                                writer.WriteLine("DefineAdditionalProperties();");
                            }
                        }
                    }

                    if (GeneratePartialPropertyDefinition)
                    {
                        writer.WriteLine();
                        writer.WriteLine("private partial void DefineAdditionalProperties();");
                    }
                }

                // Write out the model
                Spec!.SaveFile(Path.Combine("Models", $"{Name}.cs"), writer.ToString());
            });
    }
}
